package com.example.myapplication.widgets.calendar.work;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.myapplication.R;
import com.example.myapplication.utils.CollectionUtils;
import com.example.myapplication.utils.TimeUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 周表格
 * 目前文字的基准轴都是直接写死字体大小*0.82f，虽然视觉上是没什么问题，但并不严谨，后期可以修改成计算方式
 */
public class WeekTableView extends View implements GestureDetector.OnGestureListener {

    // region UI属性，间距颜色等
    private float mWidth, mHeight, mTotalWidth, mTotalHeight; // 整个试图的宽高，UI全绘制后总的宽高
    private int mAxisWidth = 120; // 左侧坐标轴列的宽度
    private float mItemHorizontalCount = 2.8f; // 横线显示多少个方格item
    private float mItemWidth, mItemMinHeight = 150; // 每一个方格item的宽高，宽度取决于mItemHorizontalCount，高度存在最小值固定
    private int mItemPadding = 3; // 方格item的内间距、两个内容item的间距
    private float mWorkWidth, mWorkHeight; // 内容的宽高，宽度取决于ItemWidth，高度取决于文字高度+mWorkPaddingVertical * 2
    private int mWorkPaddingHorizontal = 6; // 内容的横向内间距
    private int mWorkPaddingVertical = 6; // 内容的纵向间距
    private final int mStrokeWidth = 2; // 线宽
    private int mAxisTextSize = 33; // 坐标轴文字大小
    private int mAxisYTextSize = 33; // 坐标轴文字大小
    private int mWorkTextSize = 33; // 内容文字大小
    private int mBiggerWorkTextSize = mWorkTextSize; // 仅展示标题，并且显示两行时，标题一行显示的下的时候使用
    private boolean isVerticalLineToTop = true;
    private int mWeekMarginVertical = 30;
    private int mDateCircleMarginVertical = 20;
    private float mWeekHeight; // 顶部第一行，周和日期行的高度
    private int mCircleRadius = mAxisTextSize;
    private boolean isShowEllipsis;
    private static final int TITLE_INTERVAL_TO_SUB_TITLE = 10;

    private int mBackgroundColor = Color.WHITE;
    private int mAxisBackgroundColor = Color.WHITE;
    private int mCircleColor = Color.GREEN;
    private int mWeekColor = Color.LTGRAY;
    private int mDateColor = Color.BLACK;
    private int mTimeColor = Color.LTGRAY;
    private int mTimeHourColor = mTimeColor;
    private int mLineColor = Color.LTGRAY;
    // endregion

    private final String[] WEEK_ARRAY = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"};
    private String[] TIME_ARRAY = new String[]{"00:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
            "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "24:00"};
    // 在00:00之前显示一行全天的数据，但不会显示这个标题
    public static final String WHOLE_DAY_TEXT = "全天";
    private boolean isShowWholeDay;

    // 是否滚动到选择的日期
    private boolean isScrollToSelectedDate = false;
    // 是否仅展示标题
    private boolean isOnlyShowTitle;
    private boolean isShowTip = true;
    private boolean isShowSubTitle = true;
    private int mYear, mMonth, mDay; //选中的日期
    private int[] mDate; // 周下面对应显示的日期，长度8，空出第一个和列的交叉位置
    private Map<String, List<TimeWork>> mWorkMap; // 工作项，key为time

    private Paint mPaint;
    private Path mPathHorizontal, mPathVertical;
    private Map<String, Float> mPathXMap; // 记录每一根竖线的X轴，用于work绘制时设置X轴起点，key位mDate
    private Map<String, Float> mPathYMap; // 记录每一根横线的Y轴，用于work绘制时设置Y轴起点，key位mTime
    private List<TimePoint> mTimePointList; // 记录左侧时间显示的xy

    // 手势、滚动事件
    private GestureDetector gestureDetector;
    private Scroller scroller;
    private float mXOffset, mYOffset;

    private IOnClickWeekWorkItemListener mClickWorkItemListener;
    private IOnScrollListener mScrollListener;

    public WeekTableView(Context context) {
        this(context, null);
    }

    public WeekTableView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WeekTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public WeekTableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.WeekTableView);
            mBackgroundColor = typedArray.getColor(R.styleable.WeekTableView_ttv_background, mBackgroundColor);
            mAxisBackgroundColor = typedArray.getColor(R.styleable.WeekTableView_ttv_axis_background, mBackgroundColor);
            mWeekColor = typedArray.getColor(R.styleable.WeekTableView_ttv_week_color, mWeekColor);
            mDateColor = typedArray.getColor(R.styleable.WeekTableView_ttv_date_color, mDateColor);
            mTimeColor = typedArray.getColor(R.styleable.WeekTableView_ttv_time_color, mTimeColor);
            mTimeHourColor = typedArray.getColor(R.styleable.WeekTableView_ttv_time_hour_color, mTimeColor);
            mLineColor = typedArray.getColor(R.styleable.WeekTableView_ttv_line_color, mLineColor);
            mCircleColor = typedArray.getColor(R.styleable.WeekTableView_ttv_circle_color, mCircleColor);

            mItemPadding = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_item_padding, mItemPadding);
            mAxisTextSize = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_axis_size, mAxisTextSize);
            mAxisYTextSize = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_axis_size_y, mAxisTextSize);
            mWorkTextSize = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_work_size, mWorkTextSize);
            mBiggerWorkTextSize = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_bigger_work_size, mWorkTextSize);
            mWeekMarginVertical = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_week_margin_vertical, mWeekMarginVertical);
            mDateCircleMarginVertical = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_circle_margin_vertical, mDateCircleMarginVertical);
            mCircleRadius = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_circle_radius, mCircleRadius);
            mAxisWidth = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_left_axis_width, mAxisWidth);
            mItemMinHeight = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_item_height, (int) mItemMinHeight);
            mWorkPaddingHorizontal = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_work_padding_horizontal, mWorkPaddingHorizontal);
            mWorkPaddingVertical = typedArray.getDimensionPixelSize(R.styleable.WeekTableView_ttv_work_padding_vertical, mWorkPaddingVertical);

            mItemHorizontalCount = typedArray.getFloat(R.styleable.WeekTableView_ttv_item_visible_count, mItemHorizontalCount);
            isVerticalLineToTop = typedArray.getBoolean(R.styleable.WeekTableView_ttv_is_vertical_line_to_top, isVerticalLineToTop);
            isShowEllipsis = typedArray.getBoolean(R.styleable.WeekTableView_ttv_is_show_ellipsis, isShowEllipsis);
            isShowTip = typedArray.getBoolean(R.styleable.WeekTableView_ttv_is_show_tip, isShowTip);

            typedArray.recycle();
        }

        refreshTimeArray();

        mDate = new int[8];
        mPathHorizontal = new Path();
        mPathVertical = new Path();
        mTimePointList = new ArrayList<>();
        mWorkMap = new HashMap<>();
        mPathXMap = new HashMap<>();
        mPathYMap = new HashMap<>();

        mPaint = new Paint();
        mPaint.setColor(mDateColor);
        mPaint.setAntiAlias(true);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        mPaint.setTextSize(mAxisTextSize);
        mWeekHeight = (int) (2 * mWeekMarginVertical + mPaint.getTextSize() + mCircleRadius * 2 + mDateCircleMarginVertical * 2 + mStrokeWidth);

        mPaint.setTextSize(mWorkTextSize);
        if (isShowTip)
            mWorkHeight = (int) (mPaint.getTextSize() * 2 + mWorkPaddingVertical * 3);
        else
            mWorkHeight = (int) (mPaint.getTextSize() + mWorkPaddingVertical * 2);

        scroller = new Scroller(context);
        gestureDetector = new GestureDetector(context, this);
    }

    private void refreshTimeArray() {
        if (isShowWholeDay)
            TIME_ARRAY = new String[]{WHOLE_DAY_TEXT, "00:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
                    "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "24:00"};
        else
            TIME_ARRAY = new String[]{"00:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00",
                    "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "24:00"};
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mWidth = getWidth();
        mHeight = getHeight();
        mItemWidth = (int) ((mWidth - mAxisWidth - mStrokeWidth * Math.floor(mItemHorizontalCount +
                (Math.ceil(mItemHorizontalCount) == Math.floor(mItemHorizontalCount) ? 0 : 1))) / mItemHorizontalCount);
        mWorkWidth = mItemWidth - 2 * mItemPadding;
        if (mItemHorizontalCount == WEEK_ARRAY.length - 1)
            mTotalWidth = mWidth; // 因为涉及到除法转int，丢失小数，所以如果用计算后的值重新计算总值，会有几像素偏差
        else
            mTotalWidth = mAxisWidth + (mStrokeWidth + mItemWidth) * (WEEK_ARRAY.length - 1);

        createPath();
    }

    // region 对外属性设置、数据赋值等方法
    public void setOnClickWorkItemListener(IOnClickWeekWorkItemListener clickWorkItemListener) {
        mClickWorkItemListener = clickWorkItemListener;
    }

    public void setOnScrollListener(IOnScrollListener onScrollListener) {
        mScrollListener = onScrollListener;
    }

    public void setScrollToSelectedDate(boolean isScrollToSelectedDate) {
        this.isScrollToSelectedDate = isScrollToSelectedDate;
    }

    public void setShowTip(boolean showTip) {
        isShowTip = showTip;
        if (mPaint != null) {
            mPaint.setTextSize(mWorkTextSize);
            if (isShowTip)
                mWorkHeight = (int) (mPaint.getTextSize() * 2 + mWorkPaddingVertical * 3);
            else
                mWorkHeight = (int) (mPaint.getTextSize() + mWorkPaddingVertical * 2);
        }
    }

    public void setShowSubTitle(boolean showSubTitle) {
        isShowSubTitle = showSubTitle;
    }

    public void setOnlyShowTitle(boolean onlyShowTitle) {
        isOnlyShowTitle = onlyShowTitle;
    }

    public void clear() {
        mYOffset = 0;
        mWorkMap.clear();
        refresh();
    }

    public String[] setDate(String date) {
        date = date.replace("-", "/");
        String[] split = date.split("/");
        if (split.length == 3) {
            try {
                int year = Integer.parseInt(split[0]);
                int month = Integer.parseInt(split[1]);
                int day = Integer.parseInt(split[2]);
                return setDate(year, month, day);
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    // 返回第一天和最后一天的日期
    public String[] setDate(int year, int month, int day) {
        mPathXMap.clear();
        String[] firstLastDate = new String[2];
        mYear = year;
        mMonth = month;
        mDay = day;
        List<String> weekDayList = TimeUtils.getWeekDayList(String.format(Locale.getDefault(), "%4d-%02d-%02d", year, month, day));
        firstLastDate[0] = weekDayList.get(0);
        firstLastDate[1] = weekDayList.get(weekDayList.size() - 1);
        for (int i = 0; i < weekDayList.size(); i++) {
            String date = weekDayList.get(i);
            String[] split = date.split("/");
            if (split.length == 3) {
                try {
                    mDate[i + 1] = Integer.parseInt(split[2]);
                    if (isScrollToSelectedDate && mDay == mDate[i + 1]) {
                        if (i <= 1)
                            mXOffset = 0;
                        else {
                            mXOffset = -(i - 1) * mItemWidth;
                            checkOffset();
                        }
                    }
                } catch (NumberFormatException ignored) {
                }
            }
        }

        return firstLastDate;
    }

    public void setWorkList(List<TimeWork> workList) {
        mWorkMap.clear();
        addWorkList(workList);
    }

    public void addWork(TimeWork timeWork) {
        List<TimeWork> timeWorks = new ArrayList<>();
        timeWorks.add(timeWork);
        addWorkList(timeWorks);
    }

    public void addWorkList(List<TimeWork> workList) {
        if (workList != null && !workList.isEmpty()) {
            for (TimeWork timeWork : workList) {
                String hour = parseHour(timeWork.getTime());
                List<TimeWork> timeWorks = mWorkMap.get(hour);
                if (timeWorks == null) {
                    timeWorks = new ArrayList<>();
                    mWorkMap.put(hour, timeWorks);
                }
                timeWorks.add(timeWork);
            }
        }
        List<TimeWork> timeWorks = mWorkMap.get(WHOLE_DAY_TEXT);
        isShowWholeDay = CollectionUtils.isNotEmpty(timeWorks);
        refreshTimeArray();
        refresh();
    }

    public synchronized void removeWork(String id) {
        if (!TextUtils.isEmpty(id) && !mWorkMap.isEmpty()) {
            Set<String> keySet = mWorkMap.keySet();
            List<TimeWork> timeWorkList;
            List<TimeWork> removeList = new ArrayList<>();
            for (String key : keySet) {
                timeWorkList = mWorkMap.get(key);
                removeList.clear();
                if (timeWorkList != null && !timeWorkList.isEmpty()) {
                    for (TimeWork timeWork : timeWorkList) {
                        if (id.equals(timeWork.getId())) {
                            removeList.add(timeWork);
                        }
                    }
                    if (!removeList.isEmpty())
                        timeWorkList.removeAll(removeList);
                }
            }
            refresh();
        }
    }

    public Map<String, List<TimeWork>> getWorkData() {
        return mWorkMap;
    }
    // endregion

    private String parseHour(String time) {
        if (TextUtils.isEmpty(time)) return "00:00";
        String hour;

        if (!time.contains(":")) {
            if (time.length() == 1)
                hour = "0" + time + ":00";
            else
                hour = time + ":00";
        } else {
            String[] split = time.split(":");
            hour = split[0] + ":00";
        }

        // [00:00,08:00)之间的数据都显示在00:00一行
        if (hour.compareTo("00:00") >= 0 && hour.compareTo("08:00") < 0) {
            hour = "00:00";
        }
        return hour;
    }

    // 所有属性设置完之后调用进行刷新界面，如果没有手动调用方法设置属性，只在xml里面设置，则可以不需要调用刷新
    private void refresh() {
        // 还没获取到宽高，则不需要进行绘制，也绘制不到正确的试图
        if (mTotalWidth == 0 || mTotalHeight == 0) return;
        createPath();
        // 重新测量高度之后，如果当前偏移的位置操作最大高度，则需要重新绘制各个轴的位置
        if (checkOffset())
            createPath();
        invalidate();
    }

    private void createPath() {
        mPathHorizontal.reset();
        mPathVertical.reset();
        mTimePointList.clear();
        // 测量竖线
        float x;
        for (int i = 1; i < WEEK_ARRAY.length; i++) {
            x = mAxisWidth + mStrokeWidth / 2f + mStrokeWidth * (i - 1) + mItemWidth * (i - 1) + mXOffset;
            // 第一根线不用进行偏移
            if (i == 1) x -= mXOffset;
            if (x > mAxisWidth) {
                mPathVertical.moveTo(x, i == 1 || isVerticalLineToTop ? 0 : mWeekHeight);
                mPathVertical.lineTo(x, mHeight);
            }
            // 位置的记录，则还是要记录第一根线偏移之后的位置，其它基于此位置的才能绘制正确
            mPathXMap.put(String.valueOf(mDate[i]), i == 1 ? x + mXOffset : x);
        }

        // 测量横线及时间的位置
        // 固定绘制第一根线
        mPathHorizontal.moveTo(0, mWeekHeight);
        mPathHorizontal.lineTo(mWidth, mWeekHeight);
        mPathYMap.put(TIME_ARRAY[0], mWeekHeight);

        TimePoint point;
        float y = mWeekHeight + mYOffset;
        float curY;
        Map<String, Integer> mDateCount = new HashMap<>();
        int maxCount;
        mPaint.setTextSize(mAxisYTextSize);
        for (int i = 0; i < TIME_ARRAY.length; i++) {
            mDateCount.clear();
            if (i > 0) {
                curY = mItemMinHeight;
                List<TimeWork> timeWorks = mWorkMap.get(TIME_ARRAY[i - 1]);
                if (timeWorks != null && !timeWorks.isEmpty()) {
                    maxCount = 0;
                    for (TimeWork timeWork : timeWorks) {
                        Integer integer = mDateCount.get(timeWork.getDate());
                        int curCount = integer == null ? 1 : integer + 1;
                        mDateCount.put(timeWork.getDate(), curCount);
                        maxCount = Math.max(curCount, maxCount);
                    }

                    curY = maxCount * (mWorkHeight + mItemPadding) + mItemPadding;
                    if (curY < mItemMinHeight)
                        curY = mItemMinHeight;
                }
                curY += mStrokeWidth;
            } else
                curY = mStrokeWidth / 2f;
            y += curY;

            // 记录横线坐标
            mPathYMap.put(TIME_ARRAY[i], y);


            if (y > mHeight) {
                // 如果超出屏幕可视范围，则不用继续绘制了
                continue;
            } else if (y <= mWeekHeight) continue;
            if (i == TIME_ARRAY.length - 1) break; // 最后一根线和时间点不用绘制
            if (i > 0) { // 第一根线已经固定绘制了
                mPathHorizontal.moveTo(mAxisWidth, y);
                mPathHorizontal.lineTo(mWidth, y);
            }

            point = new TimePoint();
            point.time = TIME_ARRAY[i];
            point.x = mAxisWidth / 2;
            point.y = (int) (y + mPaint.getTextSize() + 10);
            mTimePointList.add(point);
        }
        mTotalHeight = y - mStrokeWidth / 2f - mYOffset;
    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);

        // 绘制工作项的背景
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mBackgroundColor);
        canvas.drawRect(mAxisWidth + mStrokeWidth, mWeekHeight + mStrokeWidth / 2f, mWidth, mHeight, mPaint);
        // 绘制工作项
        drawWork(canvas);
        // 盖一层白色背景在时间后面，遮住下面的内容
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mAxisBackgroundColor);
        canvas.drawRect(0, mWeekHeight + mStrokeWidth / 2f, mAxisWidth, mHeight, mPaint);
        // 盖一层白色背景在周和日期后面，遮住下面的内容
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mAxisBackgroundColor);
        canvas.drawRect(mAxisWidth + mStrokeWidth, 0, mWidth, mWeekHeight - mStrokeWidth / 2f, mPaint);
        // 绘制横线
        drawHorizontalAxis(canvas);
        // 绘制竖线
        drawVerticalAxis(canvas);
        // 绘制左侧时间
        drawTime(canvas);
        // 绘制周和日期
        drawWeek(canvas);
    }

    private void drawHorizontalAxis(Canvas canvas) {
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(mPathHorizontal, mPaint);
    }

    private void drawVerticalAxis(Canvas canvas) {
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(mPathVertical, mPaint);
    }

    private void drawWeek(Canvas canvas) {
        mPaint.setTextSize(mAxisTextSize);
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        float lineY = mWeekMarginVertical * 2 + mPaint.getTextSize() + mStrokeWidth / 2f;
        canvas.drawLine(mAxisWidth + mStrokeWidth, lineY, mWidth, lineY, mPaint);

        mPaint.setStyle(Paint.Style.FILL);
        float x;
        for (int i = WEEK_ARRAY.length - 1; i >= 0; i--) {
            if (i == 0)
                x = mAxisWidth / 2f;
            else {
                Float lineX = mPathXMap.get(String.valueOf(mDate[i]));
                if (lineX != null) {
                    x = lineX + mStrokeWidth / 2f + mItemWidth / 2f;
                } else
                    x = 0;
            }

            float weekWidth;
            if (mDay == mDate[i])
                weekWidth = mCircleRadius * 2;
            else {
                weekWidth = Math.max(mPaint.measureText(WEEK_ARRAY[i]), mPaint.measureText(String.valueOf(mDate[i])));
            }

            // 移动到坐标轴的不进行绘制
            if (i > 0 && x < mAxisWidth - weekWidth / 2)
                continue;

            if (i == 0) {
                // 左上角只进行绘制背景，不绘制其它
                mPaint.setColor(mAxisBackgroundColor);
                canvas.drawRect(0, 0, mAxisWidth, mWeekHeight - mStrokeWidth / 2f, mPaint);
                continue;
            }

            mPaint.setColor(mWeekColor);
            // 因为文字的绘制不是以底部为准，有个baseline，目前不进行精细计算，大致在0.82左右
            canvas.drawText(WEEK_ARRAY[i], x, mWeekMarginVertical + mPaint.getTextSize() * 0.82f, mPaint);

            mPaint.setColor(mDateColor);
            if (mDay == mDate[i]) {
                mPaint.setColor(mCircleColor);
                canvas.drawCircle(x,
                        mStrokeWidth + mWeekMarginVertical * 2 + mPaint.getTextSize() + mDateCircleMarginVertical + mCircleRadius,
                        mCircleRadius, mPaint);
                mPaint.setColor(Color.WHITE);
                canvas.drawText(String.valueOf(mDate[i]), x,
                        mStrokeWidth + (float) (mWeekMarginVertical * 2 + mPaint.getTextSize() * 1.3 + mDateCircleMarginVertical + mCircleRadius), mPaint);
                mPaint.setColor(mDateColor);
            } else {
                canvas.drawText(String.valueOf(mDate[i]), x,
                        mStrokeWidth + (float) (mWeekMarginVertical * 2 + mPaint.getTextSize() * 1.3 + mDateCircleMarginVertical + mCircleRadius), mPaint);
            }
        }
    }

    private void drawTime(Canvas canvas) {
        mPaint.setColor(mTimeColor);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(mAxisYTextSize);
        for (TimePoint point : mTimePointList) {
            String time = point.time;
            if (!TextUtils.isEmpty(time) && !time.equals(WHOLE_DAY_TEXT)) {
                if (mTimeColor != mTimeHourColor) {
                    String[] timeSplit = time.split(":");
                    if (timeSplit.length == 2) {
                        String hour = timeSplit[0];
                        String minute = ":" + timeSplit[1];
                        float totalWidth = mPaint.measureText(time);
                        float hourWidth = mPaint.measureText(hour);
                        float minuteWidth = mPaint.measureText(minute);
                        mPaint.setColor(mTimeHourColor);
                        canvas.drawText(hour, point.x - totalWidth / 2 + hourWidth / 2, point.y, mPaint);
                        mPaint.setColor(mTimeColor);
                        canvas.drawText(minute, point.x + totalWidth / 2 - minuteWidth / 2, point.y, mPaint);
                    } else
                        canvas.drawText(time, point.x, point.y, mPaint);
                } else
                    canvas.drawText(time, point.x, point.y, mPaint);
            }
        }
    }

    private void drawWork(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setTextSize(mWorkTextSize);
        Map<String, Integer> mDateCount = new HashMap<>();
        List<TimeWork> timeWorks;
        for (String key : mWorkMap.keySet()) {
            mDateCount.clear();
            timeWorks = mWorkMap.get(key);
            if (timeWorks != null && !timeWorks.isEmpty()) {
                RectF rectF;
                for (TimeWork timeWork : timeWorks) {
                    Integer integer = mDateCount.get(timeWork.getDate());
                    int curCount = integer == null ? 1 : integer + 1;
                    mDateCount.put(timeWork.getDate(), curCount);

                    Float lineX = mPathXMap.get(timeWork.getDay());
                    String hour = parseHour(timeWork.getTime());
                    Float lineY = mPathYMap.get(hour);
                    if (lineX != null && lineY != null) {
                        float top = lineY + mItemPadding * curCount + mWorkHeight * (curCount - 1);
                        if (curCount == 1)
                            top += mStrokeWidth / 2f;
                        float left = lineX + mItemPadding + mStrokeWidth / 2f;

                        if (timeWork.getRectF() == null)
                            rectF = new RectF();
                        else
                            rectF = timeWork.getRectF();
                        rectF.left = left;
                        rectF.top = top;
                        rectF.right = left + mWorkWidth;
                        rectF.bottom = top + mWorkHeight;
                        timeWork.setRectF(rectF);

                        if (top > mHeight || top < 0) {
                            // 如果超出屏幕可视范围，则不用继续绘制了
                            continue;
                        } else if (left > mWidth || left < -mItemWidth) {
                            continue;
                        }

                        mPaint.setColor(timeWork.getBgColor());
                        mPaint.setTextSize(mWorkTextSize);
                        // 内容圆角度数
                        int ROUND_ANGLE = 6;
                        canvas.drawRoundRect(rectF, ROUND_ANGLE, ROUND_ANGLE, mPaint);

                        mPaint.setColor(Color.WHITE);
                        float subTitleWidth = 0;
                        String subTitle = timeWork.getSubTitle();
                        float titleY = top + mWorkPaddingVertical + mPaint.getTextSize() * 0.82f;
                        if (!isOnlyShowTitle && isShowSubTitle && !TextUtils.isEmpty(subTitle)) {
                            subTitleWidth = mPaint.measureText(subTitle);
                            float maxWidth = mWorkWidth - 2 * mWorkPaddingHorizontal;
                            boolean isOver = false;
                            while (subTitleWidth > maxWidth && !subTitle.isEmpty()) {
                                isOver = true;
                                subTitle = subTitle.substring(0, subTitle.length() - 1);
                                subTitleWidth = mPaint.measureText(subTitle);
                            }
                            if (isShowEllipsis && isOver && subTitle.length() >= 2) {
                                Object[] res = ellipse(subTitle, maxWidth);
                                subTitle = (String) res[0];
                                subTitleWidth = (float) res[1];
                            }

                            canvas.drawText(subTitle, left + mWorkWidth - mWorkPaddingHorizontal - subTitleWidth / 2f, titleY, mPaint);
                        }

                        String title = timeWork.getTitle();
                        if (!TextUtils.isEmpty(title)) {
                            float titleWidth = mPaint.measureText(title);
                            int maxWidth = (int) (mWorkWidth - 2 * mWorkPaddingHorizontal - subTitleWidth);
                            if (subTitleWidth > 0)
                                maxWidth -= TITLE_INTERVAL_TO_SUB_TITLE;

                            boolean isOver = titleWidth > maxWidth && !title.isEmpty();
                            // 两行的情况下，如果仅展示标题，则需要进行特殊处理，标题切割成两行展示，如果一行足够，则需要显示在中间且字体大一号
                            if (isShowTip && isOnlyShowTitle) {
                                if (isOver) {
                                    // 显示两行
                                    String actTitle;
                                    float actTitleWidth = mPaint.measureText(title.substring(0, 1));
                                    float tempWidth = actTitleWidth;
                                    int index = 1;
                                    while (tempWidth <= maxWidth && index < title.length()) {
                                        actTitleWidth = tempWidth;
                                        tempWidth = mPaint.measureText(title.substring(0, ++index));
                                    }
                                    actTitle = title.substring(0, index - 1);
                                    canvas.drawText(actTitle, left + mWorkPaddingHorizontal + actTitleWidth / 2f, titleY, mPaint);

                                    if (index <= title.length()) {
                                        int startIndex = index - 1;
                                        actTitle = title.substring(startIndex, index);
                                        tempWidth = mPaint.measureText(actTitle);
                                        while (tempWidth <= maxWidth) {
                                            actTitleWidth = tempWidth;
                                            // 超过长度则退出循环
                                            if (index >= title.length())
                                                break;
                                            actTitle = title.substring(startIndex, ++index);
                                            tempWidth = mPaint.measureText(actTitle);
                                        }
                                        if (tempWidth > maxWidth) {
                                            if (isShowEllipsis && index - 2 > startIndex) {
                                                Object[] res = ellipse(actTitle, maxWidth);
                                                actTitle = (String) res[0];
                                                actTitleWidth = (float) res[1];
                                            } else {
                                                actTitle = title.substring(startIndex, index - 1);
                                            }
                                        }
                                        canvas.drawText(actTitle, left + mWorkPaddingHorizontal + actTitleWidth / 2f,
                                                top + mWorkPaddingVertical * 2 + mPaint.getTextSize() * 1.82f, mPaint);
                                    }
                                } else {
                                    // 显示一行
                                    if (mBiggerWorkTextSize != mWorkTextSize) {
                                        mPaint.setTextSize(mBiggerWorkTextSize);
                                        // 如果字体加大之后超过最大宽度了，则切换回原大小
                                        titleWidth = mPaint.measureText(title);
                                        if (titleWidth > maxWidth) {
                                            mPaint.setTextSize(mWorkTextSize);
                                        }
                                    }
                                    canvas.drawText(title, left + mWorkWidth / 2f,
                                            top + (mWorkHeight - mPaint.getTextSize()) / 2 + mPaint.getTextSize() * 0.82f, mPaint);
                                }
                            } else {
                                while (titleWidth > maxWidth && !title.isEmpty()) {
                                    title = title.substring(0, title.length() - 1);
                                    titleWidth = mPaint.measureText(title);
                                }
                                if (isShowEllipsis && isOver && title.length() >= 2) {
                                    Object[] res = ellipse(title, maxWidth);
                                    title = (String) res[0];
                                    titleWidth = (float) res[1];
                                }
                                canvas.drawText(title, left + mWorkPaddingHorizontal + titleWidth / 2f, titleY, mPaint);
                            }
                        }

                        String tip = timeWork.getTip();
                        if (isShowTip && !isOnlyShowTitle && !TextUtils.isEmpty(tip)) {
                            float tipWidth = mPaint.measureText(tip);
                            float maxWidth = mWorkWidth - 2 * mWorkPaddingHorizontal;
                            boolean isOver = false;
                            while (tipWidth > maxWidth && !tip.isEmpty()) {
                                isOver = true;
                                tip = tip.substring(0, tip.length() - 1);
                                tipWidth = mPaint.measureText(tip);
                            }
                            if (isShowEllipsis && isOver && tip.length() >= 2) {
                                Object[] res = ellipse(tip, maxWidth);
                                tip = (String) res[0];
                                tipWidth = (float) res[1];
                            }

                            canvas.drawText(tip, left + mWorkPaddingHorizontal + tipWidth / 2f,
                                    top + mWorkPaddingVertical * 2 + mPaint.getTextSize() * 1.82f, mPaint);
                        }
                    }
                }
            }
        }
    }

    private Object[] ellipse(String content, float maxWidth) {
        Object[] res = new Object[2];
        content += "...";
        float width = mPaint.measureText(content);
        while (width > maxWidth) {
            content = content.substring(0, content.length() - 4) + "...";
            width = mPaint.measureText(content);
        }
        res[0] = content;
        res[1] = width;
        return res;
    }

    private boolean checkOffset() {
        if (mScrollListener != null)
            mScrollListener.scrollToHorizontalEnd(mXOffset <= mWidth - mTotalWidth);

        Log.i("TimeTable", "mXOffset pre: " + mXOffset + " mYOffset pre: " + mYOffset);
        boolean isOver = false;
        if (mXOffset > 0) {
            mXOffset = 0;
            isOver = true;
        } else if (mXOffset < mWidth - mTotalWidth) {
            mXOffset = mWidth - mTotalWidth;
            isOver = true;
        }

        if (mYOffset > 0) {
            mYOffset = 0;
            isOver = true;
        } else if (mYOffset < mHeight - mTotalHeight) {
            mYOffset = mHeight - mTotalHeight;
            isOver = true;
        }
        Log.i("TimeTable", "mXOffset: " + mXOffset + " mYOffset: " + mYOffset);
        return isOver;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (gestureDetector.onTouchEvent(event))
            return true;
        else
            return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(@NonNull MotionEvent e) {
        scroller.forceFinished(true);
        return true;
    }

    @Override
    public void onShowPress(@NonNull MotionEvent e) {

    }

    private String mPreClickDate, mPreClickTime;

    @Override
    public boolean onSingleTapUp(@NonNull MotionEvent e) {
        if (mClickWorkItemListener == null || scroller.computeScrollOffset()) return true;
        float x = e.getX();
        float y = e.getY();
        if (y <= mWeekHeight || x <= mAxisWidth) return true;
        mPreClickDate = clickDate(x);

        String matchTime = TIME_ARRAY[0];
        String nextTime = null;
        float preLineY = mWeekHeight;
        for (String time : TIME_ARRAY) {
            Float integer = mPathYMap.get(time);
            if (integer != null) {
                if (y >= preLineY && y <= integer) {
                    nextTime = time;
                    break;
                } else {
                    matchTime = time;
                    preLineY = integer;
                }
            }
        }
        TimeWork clickItem = null;
        mPreClickTime = matchTime;
        List<TimeWork> timeWorks = mWorkMap.get(matchTime);
        List<TimeWork> workList = new ArrayList<>();
        if (timeWorks != null && !timeWorks.isEmpty())
            for (TimeWork timeWork : timeWorks) {
                if (clickItem == null && timeWork.getRectF() != null &&
                        timeWork.getRectF().contains(x, y)) {
                    clickItem = timeWork;
                }
                if (mPreClickDate.equals(timeWork.getDate().replace("/", "-")))
                    workList.add(timeWork);
            }
        mClickWorkItemListener.onClick(mPreClickDate, matchTime, nextTime, workList, clickItem);
        return true;
    }

    public List<TimeWork> getPreClickWorkList() {
        if (TextUtils.isEmpty(mPreClickTime) || TextUtils.isEmpty(mPreClickDate)) return null;
        List<TimeWork> timeWorks = mWorkMap.get(mPreClickTime);
        List<TimeWork> workList = new ArrayList<>();
        if (timeWorks != null && !timeWorks.isEmpty())
            for (TimeWork timeWork : timeWorks) {
                if (mPreClickDate.equals(timeWork.getDate().replace("/", "-")))
                    workList.add(timeWork);
            }
        return workList;
    }

    private String clickDate(float x) {
        Set<String> timeKeys = mPathXMap.keySet();
        Float lineX;
        String leftKeySet = null;
        float leftKeyX = -mTotalWidth;
        for (String timeKey : timeKeys) {
            lineX = mPathXMap.get(timeKey);
            if (lineX != null && x >= lineX) {
                if (lineX > leftKeyX) {
                    leftKeyX = lineX;
                    leftKeySet = timeKey;
                }
            }
        }
        int year = mYear;
        int month = mMonth;
        int day = leftKeySet == null ? mDate[WEEK_ARRAY.length - 1] : Integer.parseInt(leftKeySet);
        int interval = mDay - day;
        if (Math.abs(interval) > 6) {
            // 和选中的日期相差大于6，说明已经跨月了，不能直接使用选中日期的年月
            if (interval > 0) {
                if (month == 12) {
                    month = 1;
                    year++;
                } else
                    month++;
            } else {
                if (month == 1) {
                    month = 12;
                    year--;
                } else
                    month--;
            }
        }
        return String.format(Locale.getDefault(), "%02d-%02d-%02d", year, month, day);
    }

    @Override
    public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
        mXOffset -= (int) distanceX;
        mYOffset -= (int) distanceY;
        checkOffset();
        refresh();
        return true;
    }

    @Override
    public void onLongPress(@NonNull MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
        Log.i("TimeTable", "onFling mXOffset: " + mXOffset + " mYOffset: " + mYOffset +
                "  minX: " + (mWidth - mTotalWidth) + " maxX: " + 0 + " velocityX:" + velocityX +
                "  minY: " + (mHeight - mTotalHeight) + " maxY: " + 0 + " velocityY:" + velocityY);
        scroller.fling(
                (int) mXOffset, (int) mYOffset, (int) velocityX, (int) velocityY,
                (int) (mWidth - mTotalWidth), 0,
                (int) (mHeight - mTotalHeight), 0);
        invalidate();
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        Log.i("TimeTable", "currY: " + scroller.getCurrY());
        if (scroller.computeScrollOffset()) {
            mXOffset = scroller.getCurrX();
            mYOffset = scroller.getCurrY();
            checkOffset();
            refresh();
        }
    }
}
